package edu.upol.paradise.compiler.parser;

import org.apache.log4j.Logger;

public class Tokenizer implements LookAheadIterator<Token>{
	static final Logger log = Logger.getLogger(Tokenizer.class);
	
	private String source;
	private int    iter = 0;
	private Token nextToken = null;
	private String ss;
	private boolean rawMode = false;
	private int lineNo = 1;
	private int charNo = 0;
	private boolean throwErrorOnIllegalChars;
	
	public Tokenizer(String source, boolean throwErrorOnIllegalChars){
		log.debug("Initializing tokenizer with <" + source + ">");
		this.throwErrorOnIllegalChars = throwErrorOnIllegalChars;
		this.source = source;
		nextToken = nextToken();
	}

	public boolean hasNext() {
		if (nextToken == null)
			return false;
		return true;
	}

	public Token next() {
		if (nextToken == null) return null;
		Token lastToken = nextToken;
		
		if (lastToken.getTokenType() == TokenType.EOF){
			nextToken = null;
		} else 
			nextToken = nextToken();
		
		return lastToken;
	}


	private Token nextToken() {
		Token token = doGetToken(lineNo, charNo);
		return token;
	}

	private Token doGetToken(int lineNoS, int charNoS) {
		Token t = new Token(TokenType.EOF, "<end of file>", lineNoS, charNoS);
		if (iter >= source.length())
			return t;
		
		if (!rawMode)
			ss = "";
		
		boolean hasComment = false;
		boolean escape = false;
		while (true){
			if (iter == source.length()){
				t = new Token(TokenType.EOF, "<end of file>", lineNoS, charNoS);
				return t;
			}
			
			char cc = source.charAt(iter++);
			++charNo;
			if (cc == '\\')
				escape = true;
			
			if (!rawMode || (cc == '"')){
				switch (cc){
				case '(': 
					if (hasComment) break;
					if (!ss.equals("")){
						--iter;
						--charNo;
						return qualifyStringToken(ss, lineNoS, charNoS); 
					}
						
					t = new Token(TokenType.LEFT_BRACKET, "(", lineNoS, charNoS); 
					return t;
				case ')':
					if (hasComment) break;
					if (!ss.equals("")){
						--iter;
						--charNo;
						return qualifyStringToken(ss, lineNoS, charNoS); 
					}
					t = new Token(TokenType.RIGHT_BRACKET, ")", lineNoS, charNoS); 
					return t;
				case '\'': 
					if (hasComment) break;
					if (escape)
						break;
					if (!ss.equals("")){
						--iter;
						--charNo;
						return qualifyStringToken(ss, lineNoS, charNoS); 
					}
					t = new Token(TokenType.QUOTE, "'", lineNoS, charNoS); 
					return t;
				case '.': 
					if (hasComment) break;
					if (escape)
						break;
					if (ss.equals("")){
						t = new Token(TokenType.DOT, ".", lineNoS, charNoS); 
						return t; 
					} break;
				case '"': 
					if (hasComment) break;
					if (escape){
						escape = false;
						break;
					}
					if (!ss.equals("")){
						rawMode = false;
						t = qualifyStringToken(ss, lineNoS, charNoS);
						t.setTokenType(TokenType.QUOTED_STRING);
						return t;
					}
					t = new Token(TokenType.QUOTED_STRING, "\"", lineNoS, charNoS); 
					ss = "";
					rawMode = true;
					continue;
				case ' ':
				case '\r':
				case '\n':
				case '\t':
					if (cc == '\n'){
						++lineNo;
						charNo = 0;
						if (ss.equals(""))
							++lineNoS;
					}
					if (ss.equals("")){
						if (hasComment && cc == '\n')
							hasComment = false;
						continue;
					}
					return qualifyStringToken(ss, lineNoS, charNoS);
				case ';': 
					if (hasComment) break;
					if (!ss.equals("")){
						--iter;
						--charNo;
						return qualifyStringToken(ss, lineNoS, charNoS); 
					}
					hasComment = true;
				case '#':
					if (!throwErrorOnIllegalChars)
						break;
					if (charNo < source.length()){
						if (source.charAt(charNo) == ':')
							throw new RuntimeException("Illegal character sequence in source code <#:>");
					}
					break;
				}
			}
			if (!hasComment)
				ss += cc;
		}
	}

	private Token qualifyStringToken(String ss, int lineNo, int charNo) {
		try {
			int iValue = Integer.parseInt(ss);
			return new Token(TokenType.INT, iValue, lineNo, charNo);
		} catch (Exception e){
			
		}
		
		try {
			double dValue = Double.parseDouble(ss);
			return new Token(TokenType.FLOAT, dValue, lineNo, charNo);
		} catch (Exception e){
			
		}
		
		return new Token(TokenType.STRING, ss, lineNo, charNo);
	}

	public void remove() {
		
	}

	public TokenType nextTokenType() {
		return nextToken.getTokenType();
	}

	public int nextTokenLineNo() {
		return nextToken.getLineNo();
	}

	public int nextTokenCharNo() {
		return nextToken.getCharNo();
	}
	
	
}
